/*
 * TissueEnvironmentSettings.java
 */
package sim;
import java.util.*;

public class TissueEnvironmentSettings {
    /**
     * The label associated with this simulation run.
     * e.g. "sim"
     */
    public String label;
    /**
     * Gets the name of this simulation run
     * @return the name of this simulation run
     */
    public String getLabel(){
	return label;
    }
    /**
     * Sets the name of this simulation run
     * @param s name e.g. "sim"
     */
    public void setLabel(String s){
	label = s;
    }
    /**
     * The output directory (full path) associated with this simulation run. 
     * e.g. "/home/rumen/simulation"
     * All output will be created in folder "/home/rumen/simulation/sim1/"
     * "/home/rumen/simulation/sim1/sim1.clones"
     * "/home/rumen/simulation/sim1/sim1.frequencies", etc. 
     */
    public String directory;
    /**
     * Gets the name of the output folder for this simulation run
     * @return the name of the output folder for this simulation run
     */    
    public String getDirectory(){
	return directory;
    }
    /**
     * Sets the name of the output folder for this simulation run
     * @param s name e.g. "/home/rumen/simulation"
     */
    public void setDirectory(String s){
	directory = s;
    }

    /**
     * The x-dimension of the grid, or the width of a Barrett's segment. 
     */
    public int gridSizeX;
    /**
     * The y-dimension of the grid, or the height of a Barrett's segment, where the wraparound occurrs.
     * Crypts lined up at (0, 0..y_max) are neigbors to crypts lined up at (x_max, 0..y_max) 
     */
    public int gridSizeY;
    /**
     * Gets the width of the grid
     * @return the width
     */
    public int getGridSizeX() { return gridSizeX; }
    public void setGridSizeX(int x) { gridSizeX=x; }
    /**
     * Gets the height of the grid
     * @return the height
     */
    public int getGridSizeY() { return gridSizeY; }
    public void setGridSizeY(int y) { gridSizeY=y; }
    
    /**
     * If True, time is counted in generations, instead of days
     */    
    public boolean timeMode;

    /**
     * The number of days to run this simulation for
     */
    public double maxSimulationDays;
    public double fixationIntervalTime;
    public double cleanupIntervalTime;
    /**
     * The number of generations to run this simulation for
     */    
    public double maxSimulationGenerations;
    public double fixationIntervalTimeGenerations;
    public double cleanupIntervalTimeGenerations;
        
    /**
     * "Real mode" for a crypt is following the 2 rules of crypt division.
     * 1) If empty/dead neighbor crypt exists, occupy it 
     * 2) If not, displace one of six neighbors with probability r
     * Other modes are obsolete remnants from previous work.
     * In theory, a crypt can be made to follow alternative rules of division,
     * such as displacing a neighbor crypt based on the neigbor's age.
     */
    public enum CryptMode {Diversity, Space, Real};
    public CryptMode cryptMode;
    public CryptMode getCryptMode() {return cryptMode;}    
    public void setCryptMode(CryptMode mode) {cryptMode = mode;} 
    /**
     * During phylogenetic tree output, ignore tips (clones) that are below this population frequency threshold
     */
    public Double phylogenyFrequencyCutoff;

    public void setTimeMode(boolean isInGenerations){timeMode=isInGenerations;}
    public boolean getTimeMode(){return timeMode;}

    public void setFixationIntervalTime(double time){fixationIntervalTime = time;}
    public double getFixationIntervalTime(){	return fixationIntervalTime;    }
    public void setCleanupTime(double time){	cleanupIntervalTime = time;    }
    public double getCleanupTime(){	return cleanupIntervalTime;    }
    public void setMaxSimulationDays(double maxdays) {	maxSimulationDays = maxdays;    }
    public double getMaxSimulationDays() {	return maxSimulationDays;    }
    public void setMaxSimulationGenerations(double gens){maxSimulationGenerations = gens;}
    public void setFixationIntervalTimeGenerations(double gens){fixationIntervalTimeGenerations = gens;}
    public void setCleanupIntervalTimeGenerations(double gens){cleanupIntervalTimeGenerations = gens;}
    public double getMaxSimulationGenerations(){return maxSimulationGenerations;}
    public double getFixationIntervalTimeGenerations(){return fixationIntervalTimeGenerations;}
    public double getCleanupIntervalTimeGenerations(){return cleanupIntervalTimeGenerations;}
    public void setPhylogenyFrequencyCutoff(double s){phylogenyFrequencyCutoff=s;};
    public double getPhylogenyFrequencyCutoff(){return phylogenyFrequencyCutoff;};


    /**
     * Neutral, advatageous, deleterious loci for a clone
     * the list contains selection coefficients    
     * Columns are loci 
     * Rows 0, 1, 2 specify survival, reproduction and mutation selective coefficients
     * e.g. lociselectioncoefficients[30][0]=0.5 means at locus 30, the survival selective coefficient is 0.5 
     */
    public Double[][] lociSelectionCoefficients;

    /**
     * Neutral loci can be microsats. 0-neutral 1-microsat
     */
    public byte[] lociTypes;

    /**
     * Radius of crypt division dispersal
     */
    public int dispersalRadius;
    public void setDispersalRadius(int d){
	dispersalRadius = d;
    }
    public int getDispersalRadius(){
	return dispersalRadius;
    }

    /**
     * Initializes the array that stores the loci
     * @param lociCount number of neutral + selective loci
     */
    public void initializeLociSelectiveCoefficientsArray(int lociCount){
	lociSelectionCoefficients = new Double[lociCount][3];
	for(int i=0; i<lociCount; i++){
	    lociSelectionCoefficients[i][0]=0.0d;
	    lociSelectionCoefficients[i][1]=0.0d;
	    lociSelectionCoefficients[i][2]=0.0d;
	}
    }

    /**
     * Sets the number of neutral, deleterious and advantageous loci
     * such that the number of neutral loci, neutral for survival, reproduction and mutation is set
     * the number of advantageous loci for survival only, reproduction only and mutation only are set
     * the number of deleterious loci for survival only, reproduction only and mutation only are set
     *
     * @param type - Locus type, 0 through 2 for reproduction survival and  mutation 
     * @param locus - the locus index (loci 0 through n-1)
     * @param s - selection coefficient, can be negative (deleterious locus) or positive (advantageous)
     * advantageous for reproduction means faster crypt division rate
     * advantageous for survival means slower crypt death rate
     * advantageous for mutation means faster crypt mutation rate
     */
    public void setLociSelectiveCoefficients(int type, int locus, Double s){
	lociSelectionCoefficients[locus][type]=s;
    }

    public HashMap<Integer,Integer> reproductionLocusIndexToOrderIndex;
    public HashMap<Integer,Integer> survivalLocusIndexToOrderIndex;
    // Mutation is tied to division therefore pattern includes both repr and mut loci
    public HashMap<Integer,Integer> reproductionMutationLocusIndexToOrderIndex;

    /** 
     * Ignore loci having s=0.0, since those are irrelevant for creating rate classes
     * thus, we reduce the 2^n space requirement
     */
    public void compressSelectiveLoci(){
	reproductionLocusIndexToOrderIndex = new HashMap<Integer,Integer>();
	survivalLocusIndexToOrderIndex = new HashMap<Integer,Integer>();
	reproductionMutationLocusIndexToOrderIndex = new HashMap<Integer,Integer>();

	int j=0;
	for(int i=0; i<lociSelectionCoefficients.length; i++){
	    //System.out.println("rep locus "+i+" "+lociSelectionCoefficients[i][0]);
	    if(lociSelectionCoefficients[i][0]!=0.0d){
		//System.out.println("set with index " + j);
		// i - locus, j- order from 0 to N for N selective loci
		reproductionLocusIndexToOrderIndex.put(new Integer(i), new Integer(j));
		j++;
	    }
	}
	j=0;
	for(int i=0; i<lociSelectionCoefficients.length; i++){
	    if(lociSelectionCoefficients[i][1]!=0.0d){
		// i - locus, j- order from 0 to N for N selective loci
		survivalLocusIndexToOrderIndex.put(new Integer(i), new Integer(j));
		j++;
	    }
	}
	j=0;
	for(int i=0; i<lociSelectionCoefficients.length; i++){
	    // Since repr and mut are tied, all loci s!=0 for repr and mut are relevant 
	    if(lociSelectionCoefficients[i][0]!=0.0d || lociSelectionCoefficients[i][2]!=0.0d){
		// i - locus, j- order from 0 to N for N selective loci
		reproductionMutationLocusIndexToOrderIndex.put(new Integer(i), new Integer(j));
		j++;
	    }
	}
    }

    public Double[] getSelectiveCoefficientsOfReproductionLoci(){
	ArrayList<Double> res = new ArrayList<Double>();
	for(int i=0; i<lociSelectionCoefficients.length; i++){
	    if(lociSelectionCoefficients[i][0]!=0.0d){
		res.add(new Double(lociSelectionCoefficients[i][0]));
	    }
	}	
	Double[] a = res.toArray(new Double[res.size()]);
	return a;
    }

    public Double[] getSelectiveCoefficientsOfSurvivalLoci(){
	ArrayList<Double> res = new ArrayList<Double>();
	for(int i=0; i<lociSelectionCoefficients.length; i++){
	    if(lociSelectionCoefficients[i][1]!=0.0d){
		res.add(new Double(lociSelectionCoefficients[i][1]));
	    }
	}	
	Double[] a = res.toArray(new Double[res.size()]);
	return a;
    }

    public Double[] getSelectiveCoefficientsOfMutationLoci(){
	ArrayList<Double> res = new ArrayList<Double>();
	for(int i=0; i<lociSelectionCoefficients.length; i++){
	    if(lociSelectionCoefficients[i][2]!=0.0d){
		res.add(new Double(lociSelectionCoefficients[i][2]));
	    }
	}	
	Double[] a = res.toArray(new Double[res.size()]);
	return a;
    }

    /**
     * Get only loci that have s!=0.0
     * @param type - Locus type, 0 through 2 for survival reproduction mutation 
     */
    public Double[] getSelectiveLociSelectiveCoefficients(int type){
	ArrayList<Double> res = new ArrayList<Double>();
	for(int i=0; i<lociSelectionCoefficients[type].length; i++){
	    if(lociSelectionCoefficients[type][i]!=0.0){
		res.add(lociSelectionCoefficients[type][i]);
	    }
	}
	return res.toArray(new Double[res.size()]);
    }
    
    /** 
     * Sets which loci will be microsats
     */
    public void setLociTypes(int totalLoci, int startIndex, int endIndex){
	lociTypes = new byte[totalLoci];
	for(int i=0;i<totalLoci;i++){
	    lociTypes[i]=0;
	    // Set microsat
	    if(i>=startIndex&&i<endIndex){
		lociTypes[i]=1;
	    }
	}
    }


    /**
     * Returns the array of sel. coeff. Used so that all crypts in the grid point to only one instance of the array
     */
    public Double[][] getLoci(){ return lociSelectionCoefficients; }
    /**
     * Returns the array of sel. coeff. Used so that all crypts in the grid point to only one instance of the array
     */
    public int getLociCount(){ return lociSelectionCoefficients.length; }
    /**
     * Returns the selective coefficient for survival at locus index
     * @param index The index of the locus
     */    
    public Double getLocusSurvivalS(int index){ return lociSelectionCoefficients[index][0]; }
    /**
     * Returns the selective coefficient for reproduction at locus index
     * @param index The index of the locus
     */    
    public Double getLocusReproductionS(int index){ return lociSelectionCoefficients[index][1]; }
    /**
     * Returns the selective coefficient for mutation at locus index
     * @param index The index of the locus
     */    
    public Double getLocusMutationS(int index){ return lociSelectionCoefficients[index][2]; }

    // Microsat
    public byte getLocusType(int index){
	return lociTypes[index];
    }

    public int getMSLociSize(){
	int res = 0;
	for(int i=0;i<lociTypes.length;i++){
	    if(lociTypes[i]==1){
		res+=1;
	    }
	}
	return res;
    }

    
    /**
     * Rules of crypt division 1) If empty/dead neighbor crypt exists, occupy it 2) If not, displace one of six neighbors with probability r
     */    
    public Double cryptDivisionProbability;
    public void setCryptDivisionProbability(Double x){
	cryptDivisionProbability = x;
    }
    /**
     * @return the probability of displacing an alive neighbor
     */
    public Double getCryptDivisionProbability(){
	return cryptDivisionProbability;
    }

    /**
     * Replication-dependent mutation k=1, or replication-independent k=0
     */    
    public Double replicationDependenceFactor;
    public void setReplicationDependenceFactor(Double x){
	replicationDependenceFactor = x;
    }
    /**
     * @return k When mutation is replication-dependent k=1, when it is replication-independent k=0
     */
    public Double getReplicationDependenceFactor(){
	return replicationDependenceFactor;
    }



    /**
     * Time to crypt division in days
     */
    public Double basalCryptDivisionRate;
    /**
     * Fastest time to division in days ~1 day
     */
    public Double fastestCryptDivisionRate;
    /**
     * Slowest time to division ~200 days
     */
    public Double slowestCryptDivisionRate;
    public Double getBasalCryptDivisionRate() {
	return basalCryptDivisionRate;
    }    
    public Double getFastestCryptDivisionRate() {
	return fastestCryptDivisionRate;
    }
    public Double getSlowestCryptDivisionRate() {
	return slowestCryptDivisionRate;
    }
    public void setBasalCryptDivisionRate(Double x) {
	basalCryptDivisionRate = x;
    }
    public void setFastestCryptDivisionRate(Double x) {
	fastestCryptDivisionRate = x;
    }
    public void setSlowestCryptDivisionRate(Double x) {
	slowestCryptDivisionRate = x;
    }


    /**
     * Time to crypt death in days
     */
    public Double basalCryptDeathRate;
    /**
     * Fastest time to death in days ~1 day
     */
    public Double fastestCryptDeathRate;
    /**
     * Slowest time to death ~200 days
     */
    public Double slowestCryptDeathRate;
    public Double getBasalCryptDeathRate() {
	return basalCryptDeathRate;
    }    
    public Double getFastestCryptDeathRate() {
	return fastestCryptDeathRate;
    }
    public Double getSlowestCryptDeathRate() {
	return slowestCryptDeathRate;
    }
    public void setBasalCryptDeathRate(Double x) {
	basalCryptDeathRate = x;
    }
    public void setFastestCryptDeathRate(Double x) {
	fastestCryptDeathRate = x;
    }
    public void setSlowestCryptDeathRate(Double x) {
	slowestCryptDeathRate = x;
    }

    /**
     * Time to crypt neutralMutation in days
     */
    public Double basalCryptNeutralMutationRate;
    /**
     * Fastest time to neutralMutation in days ~1,000 days /neutral rate per locus is 10^-3/
     */
    public Double fastestCryptNeutralMutationRate;
    /**
     * Slowest time to neutralMutation ~100,000,000 days /neutral rate per locus is 10^-8/
     */
    public Double slowestCryptNeutralMutationRate;
    public Double getBasalCryptNeutralMutationRate() {
	return basalCryptNeutralMutationRate;
    }    
    public Double getFastestCryptNeutralMutationRate() {
	return fastestCryptNeutralMutationRate;
    }
    public Double getSlowestCryptNeutralMutationRate() {
	return slowestCryptNeutralMutationRate;
    }
    /**
     * Sets the "neutral genome" mutation rate per day
     * @param x Basal neutral mutation rate
     * @param lociCount number of neutral loci     
     */
    public void setBasalCryptNeutralMutationRate(Double x, int lociCount) {
	basalCryptNeutralMutationRate = x*lociCount;
    }
    public void setFastestCryptNeutralMutationRate(Double x, int lociCount) {
	fastestCryptNeutralMutationRate = x*lociCount;
    }
    public void setSlowestCryptNeutralMutationRate(Double x, int lociCount) {
	slowestCryptNeutralMutationRate = x*lociCount;
    }

    /**
     * Time to crypt selectiveMutation in days
     */
    public Double basalCryptSelectiveMutationRate;
    /**
     * Fastest time to selectiveMutation in days ~100,000 days /selective rate per locus is 10^-5/
     */
    public Double fastestCryptSelectiveMutationRate;
    /**
     * Slowest time to selectiveMutation ~100,000,000 days /selective rate per locus is 10^-8/
     */
    public Double slowestCryptSelectiveMutationRate;
    public Double getBasalCryptSelectiveMutationRate() {
	return basalCryptSelectiveMutationRate;
    }    
    public Double getFastestCryptSelectiveMutationRate() {
	return fastestCryptSelectiveMutationRate;
    }
    public Double getSlowestCryptSelectiveMutationRate() {
	return slowestCryptSelectiveMutationRate;
    }
    /**
     * Sets the "selective genome" mutation rate per day
     * @param x Basal selective mutation rate
     * @param lociCount number of selective loci     
     */
    public void setBasalCryptSelectiveMutationRate(Double x, int lociCount) {
	basalCryptSelectiveMutationRate = x*lociCount;
    }
    public void setFastestCryptSelectiveMutationRate(Double x, int lociCount) {
	fastestCryptSelectiveMutationRate = x*lociCount;
    }
    public void setSlowestCryptSelectiveMutationRate(Double x, int lociCount) {
	slowestCryptSelectiveMutationRate = x*lociCount;
    }

    /**
     * Returns a "0000000" string of length n, representing no mutations at all n reproductive loci  
     * @return wild type mutation states pattern
     */    
    public String getWildTypeReproductionPattern(){
	String s = new String();
	for(int i=0; i<reproductionLocusIndexToOrderIndex.size(); i++){
	    s = s + "0";
	}
	return s;
    }

    /**
     * Same as getWildTypeReproductionPattern() for survival
     * @return wild type mutation states pattern
     */    
    public String getWildTypeSurvivalPattern(){
	String s = new String();
	for(int i=0; i<survivalLocusIndexToOrderIndex.size(); i++){
	    s = s + "0";
	}
	return s;
    }

    /**
     * Same as getWildTypeReproductionPattern() for reproductionMutation
     * @return wild type mutation states pattern
     */    
    public String getWildTypeReproductionMutationPattern(){
	String s = new String();
	for(int i=0; i<reproductionMutationLocusIndexToOrderIndex.size(); i++){
	    s = s + "0";
	}
	return s;
    }
}
